package com.ppc.utils;

import com.ppc.entity.MSKFile;
import com.ppc.entity.PKFile;
import com.ppc.exception.BaseException;
import com.ppc.pmatch.Ciphertext;
import com.ppc.pmatch.MSKObj;
import com.ppc.pmatch.PKObj;
import com.ppc.pmatch.SKObj;
import com.ppc.pmatch.SKPO;
import com.ppc.pmatch.SetupObj;
import com.ppc.pmatch.ShareObj;
import com.ppc.pmatch.Trapdoor;
import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Pairing;
import it.unisa.dia.gas.plaf.jpbc.pairing.PairingFactory;
import it.unisa.dia.gas.plaf.jpbc.util.io.Base64;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

/**
 * @author dy
 * @date 2023/12/20
 * @decreption
 */
@Slf4j
public class PMatchUtil {

  private static final Pairing group = PairingFactory.getPairing(getPropertiesPath());
  private static final int userNum = 2001;
  private static final int k = 2;
  private static PKObj PKGen;
  private static MSKObj MSK;
  private static boolean hasSetup = false;


  /**
   * @return boolean
   * @description
   */
  public static boolean getHasSetup() {
    return hasSetup;
  }

  /**
   * @param hasSetup
   * @description
   */
  public static void setHasSetup(boolean hasSetup) {
    PMatchUtil.hasSetup = hasSetup;
  }


  /**
   * @return {@link SetupObj }
   * @description
   */
  public static SetupObj initiatSystem() {
    SecretShare ss = new SecretShare(group, true);
    SetupObj setupObj = setup(ss, k, userNum);
    PKGen = setupObj.getPK();
    hasSetup = true;
    return setupObj;
  }


  /**
   * @param tag
   * @return {@link Ciphertext }
   * @description
   */
  public static Ciphertext indexGen(String tag) {

    Ciphertext C = enc(tag);
    return C;
  }

  /**
   * @param SK
   * @param tags
   * @return {@link List }<{@link Trapdoor }>
   * @description
   */
  public static List<Trapdoor> trapWorker(SKObj SK, String[] tags) {
    List<Trapdoor> list = new ArrayList<Trapdoor>();
    for (String tag : tags) {
      list.add(trap(SK, tag));
    }
    return list;
  }

  /**
   * 加密
   *
   * @param SK
   * @param w
   * @return {@link Trapdoor }
   * @description
   */
  public static Trapdoor trap(SKObj SK, String w) {
    PKFile pkFile = (PKFile) readPKFromFile();
    PKObj PK = null; // PKFile
    try {
      PK = PKFiletoPKObj(pkFile);
    } catch (IOException e) {
      throw new BaseException("读取PK错误");
    }
    Element D = SK.getD();
    Element E = SK.getE();
    Element s = group.getZr().newRandomElement().getImmutable();
    Element T1 = PK.getG1().duplicate().powZn(s);
    byte[] bytes = w.getBytes();
    Element T2 = group.getG1().newElementFromHash(bytes, 0, bytes.length).powZn(s);
    Element T3 = E.duplicate().powZn(s);
    Element T4 = D.duplicate().powZn(s);
    String s1 = Base64.encodeBytes(T1.toBytes());
    String s2 = Base64.encodeBytes(T2.toBytes());
    String s3 = Base64.encodeBytes(T3.toBytes());
    String s4 = Base64.encodeBytes(T4.toBytes());
    Trapdoor T = new Trapdoor(s1, s2, s3, s4);
    return T;

  }

  /**
   * @param SK
   * @return {@link SKObj }
   * @description
   */
  public static SKObj SKPOtoSKObj(SKPO SK) throws IOException { // string2elemnt
    Element D = group.getG1().newElementFromBytes(Base64.decode(SK.getD()));
    Element E = group.getG1().newElementFromBytes(Base64.decode(SK.getE()));
    return new SKObj(SK.getI(), D, E); // 作为密钥Obj就不加了
  }

  /**
   * 获取SK对象
   *
   * @param SK
   * @return {@link SKPO }
   * @description
   */
  public static SKPO SKObjtoSKPO(SKObj SK) { // element2string
    String D = Base64.encodeBytes(SK.getD().toBytes());
    String E = Base64.encodeBytes(SK.getE().toBytes());
    return new SKPO(SK.getI(), D, E, LocalDateTime.now());  // SK存入数据库时加入时间
  }

  /**
   * @param PK
   * @return {@link PKObj }
   * @description
   */
  public static PKObj PKFiletoPKObj(PKFile PK) throws IOException { // string2elemnt
    Element g = group.getG1().newElementFromBytes(Base64.decode(PK.getG()));
    Element g1 = group.getG1().newElementFromBytes(Base64.decode(PK.getG1()));
    Element g2 = group.getG1().newElementFromBytes(Base64.decode(PK.getG2()));
    Element ek = group.getG1().newElementFromBytes(Base64.decode(PK.getEk()));

    return new PKObj(g, g1, g2, ek, PK.getGenerateTime());
  }

  /**
   * PK转换
   *
   * @param PK
   * @return {@link PKFile }
   * @description
   */
  public static PKFile PKObjtoPKFile(PKObj PK) { // element2string
    String g = Base64.encodeBytes(PK.getG().toBytes());
    String g1 = Base64.encodeBytes(PK.getG1().toBytes());
    String g2 = Base64.encodeBytes(PK.getG2().toBytes());
    String ek = Base64.encodeBytes(PK.getEk().toBytes());
    return new PKFile(g, g1, g2, ek, LocalDateTime.now());
  }

  /**
   * MSK转换
   *
   * @param MSK
   * @return {@link MSKFile }
   * @description
   */
  public static MSKFile MSKObjtoMSKFile(MSKObj MSK) { // element2string
    String x1 = Base64.encodeBytes(MSK.getX1().toBytes());
    String x2 = Base64.encodeBytes(MSK.getX2().toBytes());
    String f1 = Base64.encodeBytes(MSK.getF1().toBytes());
    String t = Base64.encodeBytes(MSK.getT().toBytes());
    return new MSKFile(x1, x2, f1, t, MSK.getGenerateTime());
  }

  /**
   * @param MSK
   * @return {@link MSKObj }
   * @description
   */
  public static MSKObj MSKFiletoMSKObj(MSKFile MSK) throws IOException { // string2elemnt
    Element x1 = group.getG1().newElementFromBytes(Base64.decode(MSK.getX1()));
    Element x2 = group.getG1().newElementFromBytes(Base64.decode(MSK.getX2()));
    Element f1 = group.getG1().newElementFromBytes(Base64.decode(MSK.getF1()));
    Element t = group.getG1().newElementFromBytes(Base64.decode(MSK.getT()));
    return new MSKObj(x1, x2, f1, t, MSK.getGenerateTime());
  }


  /**
   * 获取父目录路径
   *
   * @return {@link String }
   * @description
   */
  public static String getParentDirPath() {
    File currentDir = new File(".");
    try {
      String parentDirPath = currentDir.getCanonicalFile().getParent();
      return parentDirPath;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 写PK到文件
   *
   * @param obj
   * @description
   */
  public static void writePKToFile(Object obj) {
    File currentDir = new File(".");
    try {
      String parentDirPath = currentDir.getCanonicalFile().getParent();
      File file = new File(parentDirPath + "/ppc-common/src/main/resources/PK.dat");
      if (file.exists()) {
        System.out.println("文件已存在");
      } else {
        try {
          file.createNewFile();
          System.out.println(file.getAbsolutePath());
          FileOutputStream out;
          out = new FileOutputStream(file);
          ObjectOutputStream objOut = new ObjectOutputStream(out);
          objOut.writeObject(obj);
          objOut.flush();
          objOut.close();
          System.out.println("write object success!");
          log.info("PK:{}", obj);
        } catch (IOException e) {
          System.out.println("write object failed");
          file.delete();
          e.printStackTrace();
        }
      }
    } catch (IOException e) {
      throw new BaseException("没有上一级");
    }
  }

  /**
   * 从文件读PK
   *
   * @return {@link PKFile }
   * @description
   */
  public static Object readPKFromFile() {
    Object temp = null;
    try {
      File file = new File(getPKPath()); //TODO 改成jar包形式路径
      FileInputStream in;
      try {
        in = new FileInputStream(file);
        ObjectInputStream objIn = new ObjectInputStream(in);
        temp = objIn.readObject();
        objIn.close();
        System.out.println("read object success!");
      } catch (ClassNotFoundException e) {
        System.out.println("read object failed");
        e.printStackTrace();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return (PKFile) temp;
  }

  /**
   * 写MSK
   *
   * @param obj
   * @description
   */
  public static void writeMSKToFile(Object obj) {
    File currentDir = new File(".");
    try {
      String parentDirPath = currentDir.getCanonicalFile().getParent();
      File file = new File(parentDirPath + "/ppc-common/src/main/resources/MSK.dat");
      if (file.exists()) {
        System.out.println("文件已存在");
      } else {
        try {
          file.createNewFile();
          System.out.println(file.getAbsolutePath());
          FileOutputStream out;
          out = new FileOutputStream(file);
          ObjectOutputStream objOut = new ObjectOutputStream(out);
          objOut.writeObject(obj);
          objOut.flush();
          objOut.close();
          System.out.println("write object success!");
          log.info("MSK:{}", obj);
        } catch (IOException e) {
          System.out.println("write object failed");
          file.delete();
          e.printStackTrace();
        }
      }
    } catch (IOException e) {
      throw new BaseException("没有上一级");
    }
  }

  /**
   * 读MSK
   *
   * @return {@link Object }
   * @description
   */
  public static Object readMSKFromFile() {
    File currentDir = new File(".");
    Object temp = null;

    String parentDirPath = null;
    try {
      parentDirPath = currentDir.getCanonicalFile().getParent();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    File file = new File(parentDirPath + "/ppc-common/src/main/resources/MSK.dat");
    FileInputStream in;
    try {
      in = new FileInputStream(file);
      ObjectInputStream objIn = new ObjectInputStream(in);
      temp = objIn.readObject();
      objIn.close();
      System.out.println("read object success!");
    } catch (IOException e) {
      System.out.println("read object failed");
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    return temp;

  }


  /**
   * @param tag
   * @return {@link Ciphertext }
   * @description 加密需求keyword
   */
  public static Ciphertext enc(String tag) {
    PKFile pkFile = (PKFile) readPKFromFile();
    // PKFile pkFile = new PKFile("c9DkSivdfJoj3P+VWJ+CMRUdRxv5fFwym9nd5iqe0voGqRrCg3qhxVipe3yYN863tEWTWTGs8D8cUv2R1zuCHJPW1YOksGHUB9IuKOxZ9eM5UMRZjlmOYGPcjSCkNtKruxhabrVy+/d46r8XwwcV0eJsxNVvxMdGY6+MmCAuVIg=", "nlQuru3a/jq2XsP1kKBmatLN53HSn1qBPkehxi2iEzemUjODGiktVu0qlcmSzHtOaslJwp4g8o2q8of/lYei1Ih7apmy7ZqonCN1O1ECeVhNHRFSUwjAwawZIrZHx4v80Y18x8wJH4eOpGXF8NFTAEmYm7OPZO7PJIWDSNLV+Fc=", "QhHXRiUg/d8sRy7XGrBwU+rksfOdpNuXTHkMA/CEVXedTc6P5hs0Z6Gv0MPtwuPpzdNJDeapnWtaAxA9PUakiVHCmyuh4k/2D5h8XDekeuC6nBbAi/oIecJWyCt4qPNzZgC6iFksikWair0yHPpBEjKaQt8KRI3TO2VIjP2RC3M=", "GBFei95pUUqhomlx51Q7LjXLyvApV5u8HEINflgkGVcBXC9NXN7SK0D1usAcHVANZwLqINgxCOEWWFkH2sa0OmNsUW6XgMG70M1nwZH++8Jj2wbXWTZvPee1SvSQ9K+FoA/GoIQ1tbhA3LvceWz1LPgL6PQfZMaXMvQBLwST69Q=", LocalDateTime.parse("2023-11-26T16:33:32.694768700"));
    PKObj PK = null; //PKFile
    try {
      PK = PKFiletoPKObj(pkFile);
    } catch (IOException e) {
      throw new BaseException("读取PK错误");
    }
    Element r1 = group.getZr().newRandomElement().getImmutable();
    Element r2 = group.getZr().newRandomElement().getImmutable();
    byte[] bytes = tag.getBytes();
    Element C1 = PK.getG2().duplicate().powZn(r2).mul(group.getG1().newElementFromHash(bytes, 0,
        bytes.length).powZn(r1));
    Element C2 = PK.getG1().duplicate().powZn(r1);
    Element C3 = PK.getEk().duplicate().powZn(r2);
    Element C4 = PK.getG().duplicate().powZn(r2);
    String s1 = Base64.encodeBytes(C1.toBytes());
    String s2 = Base64.encodeBytes(C2.toBytes());
    String s3 = Base64.encodeBytes(C3.toBytes());
    String s4 = Base64.encodeBytes(C4.toBytes());
    Ciphertext C = new Ciphertext(s1, s2, s3, s4); // 密文，但公钥私钥不用转密文
    return C;
  }

  /**
   * @param ss
   * @param k
   * @param n
   * @return {@link SetupObj }
   * @description
   */

  /*启动函数*/
  public static SetupObj setup(SecretShare ss, int k, int n) {
    Element g = group.getG1().newRandomElement().getImmutable();
    Element x1 = group.getZr().newRandomElement().getImmutable();
    Element x2 = group.getZr().newRandomElement().getImmutable();
    Element g1 = g.powZn(x1).getImmutable();
    Element g2 = g.powZn(x2).getImmutable();
    SecretShare s = new SecretShare(group, true);
    ShareObj shareObj = s.genShares(x1, k, n + 1);
    List<Element> q = shareObj.getQ();
    List<Element> shares = shareObj.getShares();
    Element t = group.getZr().newOneElement().getImmutable();
    Element EK = g.powZn(shares.get(1).duplicate().div(x1)).getImmutable();
    PKObj PK = new PKObj(g, g1, g2, EK, LocalDateTime.now());
    MSKObj MSK = new MSKObj(x1, x2, q.get(1), t, LocalDateTime.now());  // 都有创建时间
    List<SKObj> SKSET = new ArrayList<SKObj>();
    for (int i = 2; i < n + 2; i++) {
      Element e1 = group.getZr().newOneElement().getImmutable();
      Element e2 = group.getZr().newElement(i).getImmutable();
      Element result1 = e1.negate().div(e2.sub(e1));
      Element result2 = e2.negate().div(e1.sub(e2));
      Element D = g2.powZn(shares.get(i).duplicate().mul(result1));
      Element E = g2.powZn(x1.mul(result2));
      SKObj SK = new SKObj(i, D, E);
      SKSET.add(SK);
    }
    SetupObj setupObj = new SetupObj(PK, MSK, SKSET);
    return setupObj;
  }

  /**
   * 匹配函数
   *
   * @param C
   * @param T
   * @return boolean
   * @description
   */
  public boolean match(Map<String, Element> C, Map<String, Element> T) {
    boolean flag = false;
    Element left = group.pairing(C.get("C1"), T.get("T1"));
    Element right1 = group.pairing(C.get("C2"), T.get("T2"));
    Element right2 = group.pairing(C.get("C3"), T.get("T3"));
    Element right3 = group.pairing(C.get("C4"), T.get("T4"));
    Element right = right1.duplicate().mul(right2).duplicate().mul(right3);
    if (left.isEqual(right)) {
      flag = true;
    }
    return flag;

  }

  /**
   * 匹配索引
   *
   * @param I
   * @param T
   * @return int
   * @description
   */
  public int matchIndex(List<List<Map<String, Element>>> I, Map<String, Element> T) {
    int res = 0;
    for (int i = 0; i < I.size(); i++) {
      List<Map<String, Element>> CC = I.get(i); //顺序列表
      for (int j = 0; j < CC.size(); j++) {
        Map<String, Element> C = CC.get(j);
        boolean flag = match(C, T);
        if (flag == true) {
          res++;
        }
      }
    }
    return res;
  }

  /**
   * 面向多模块工程的读取properties功能
   */
  public static String getPropertiesPath() {
    Resource resource = new ClassPathResource("a.properties");
    try {
      InputStream inputStream = resource.getInputStream();
      File tempFile = File.createTempFile("temp", ".properties");
      FileUtils.copyInputStreamToFile(inputStream, tempFile);
      String s = tempFile.getAbsolutePath();
      log.info("propertiesPath={}",s);
      return s;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 获取PK文件路径
   */
  public static String getPKPath() {
    Resource resource = new ClassPathResource("PK.dat");
    try {
      InputStream inputStream = resource.getInputStream();
      File tempFile = File.createTempFile("temp", ".dat");
      FileUtils.copyInputStreamToFile(inputStream, tempFile);
      String s = tempFile.getAbsolutePath();
      return s;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 获取MSK路径
   */
  public static String getMSKPath() {
    File inFile = new File("jdbc/MSK.dat");
    //read(inFile);
    try { // 复制到模块外的文件
      if (!inFile.exists()) {
        log.info("generate property");
        InputStream stream = PMatchUtil.class.getClassLoader()
            .getResourceAsStream("MSK.dat"); // 读类下的资源文件
        FileUtils.copyInputStreamToFile(stream, inFile);
      }
    } catch (IOException e) {
      log.error(e.getMessage());
    }
    System.out.println(inFile.getAbsolutePath());
    return inFile.getAbsolutePath();
  }


  /**
   * @param args
   * @description
   */
  public static void main(String[] args) {
    PKFile pkFile = (PKFile) readPKFromFile();
    System.out.println(pkFile);
  }

}
